﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the GNU General Public License version 2 (GPLv2).
// See accompanying file License.txt or copy at http://phuse.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Phuse.Net.Forums
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.IO;
    using System.Threading;
    using System.Threading.Tasks;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    using Phuse.Mime;
    using Phuse.Net.Nntp;
    using Phuse.UnitTesting;

    /// <summary>Provides the common test infrastructure.</summary>
    public abstract class TestInfrastructure : TestBase
    {
#if WINDOWS_PHONE
        internal const string OurDirectory = "Data";
#else
        internal static readonly string OurDirectory =
            Path.GetDirectoryName(typeof(TestInfrastructure).Assembly.ManifestModule.FullyQualifiedName);
#endif

        internal const string OurGroupName = "our.group";

        internal IHost AddRandomHost(Cache cache)
        {
            var name = GetUniqueString();
            var port = this.GetRandomPort();
            var userName = GetUniqueString();
            var emailAddress = GetUniqueString();
            var accountName = GetUniqueString();
            var password = GetUniqueString();
            var host = cache.AddHost<NntpForumProvider, NntpConnection>(
                name, port, userName, emailAddress, accountName, password);
            cache.SaveChanges();
            Assert.AreEqual(name, host.Name);
            Assert.AreEqual(port, host.Port);
            Assert.AreEqual(userName, host.UserName);
            Assert.AreEqual(emailAddress, host.EmailAddress);
            Assert.AreEqual(accountName, host.AccountName);
            Assert.AreEqual(password, host.Password);
            userName = GetUniqueString();
            emailAddress = GetUniqueString();
            accountName = GetUniqueString();
            password = GetUniqueString();
            host.UserName = userName;
            host.EmailAddress = emailAddress;
            host.AccountName = accountName;
            host.Password = password;
            cache.SaveChanges();
            Assert.AreEqual(userName, host.UserName);
            Assert.AreEqual(emailAddress, host.EmailAddress);
            Assert.AreEqual(accountName, host.AccountName);
            Assert.AreEqual(password, host.Password);
            return host;
        }

        internal IGroup AddRandomGroup(Cache cache, IHost host)
        {
            string name = GetUniqueString();
            string differentName = GetUniqueString();
            int newInboundArticlesReceiveLimit = this.GetRandomNewInboundArticlesReceiveLimit();
            int differentNewInboundArticlesReceiveLimit = newInboundArticlesReceiveLimit + 1;
            int cachedInboundArticlesLimit = this.Random.Next(newInboundArticlesReceiveLimit, int.MaxValue);
            var filter = new InboundArticleFilter(
                this.GetRandomBool(), this.GetRandomBool(), this.GetRandomBool(), this.GetRandomBool());
            var differentFilter = new InboundArticleFilter(
                Shift(filter.IsOriginalBeingWatched),
                Shift(filter.HasOriginalBeenRead),
                Shift(filter.IsFollowupBeingWatched),
                Shift(filter.HasFollowupBeenRead));
            Assert.IsTrue(filter != differentFilter);
            Assert.IsFalse(filter.Equals(null));
            Assert.IsFalse(filter.Equals((object)differentFilter));

            var group = host.AddGroup(name, name, newInboundArticlesReceiveLimit, cachedInboundArticlesLimit);
            group.InboundArticleFilter = filter;
            cache.SaveChanges();

            AssertEqual(group, host.Name, name, name, newInboundArticlesReceiveLimit, cachedInboundArticlesLimit, filter);
            group.DisplayName = differentName;
            group.NewInboundArticlesReceiveLimit = differentNewInboundArticlesReceiveLimit;
            group.InboundArticleFilter = differentFilter;
            cache.SaveChanges();
            AssertEqual(
                group,
                host.Name,
                name,
                differentName,
                differentNewInboundArticlesReceiveLimit,
                cachedInboundArticlesLimit,
                differentFilter);
            return group;
        }

        [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "The caller is responsible for disposal.")]
        internal static Cache CreateNewCache()
        {
            var dataDirectory = Path.Combine(OurDirectory, GetUniqueString());
            Cache.CreateCache(dataDirectory);
            var result = new Cache(dataDirectory, new NullCryptoProvider());
            Assert.AreEqual(dataDirectory, result.DataDirectory);
            Assert.IsTrue(result.HostNameMaxLength > 0);
            Assert.IsTrue(result.UserNameMaxLength > 0);
            Assert.IsTrue(result.EmailAddressMaxLength > 0);
            Assert.IsTrue(result.AccountNameMaxLength > 0);
            Assert.IsTrue(result.PasswordMaxLength > 0);
            Assert.IsTrue(result.GroupNameMaxLength > 0);
            Assert.IsTrue(result.GroupDisplayNameMaxLength > 0);
            Assert.IsTrue(result.ArticleSubjectMaxLength > 0);
            return result;
        }

        internal static IHost CreateGmaneHost(Cache cache)
        {
            var host = cache.AddHost<NntpForumProvider, NntpConnection>("news.mozilla.org", 119);
            cache.SaveChanges();
            return host;
        }

        internal static IGroup CreateBoostDeveloperGroup(
            Cache cache, IHost host, int newInboundArticlesReceiveLimit, int cachedInboundArticlesLimit)
        {
            var group = host.AddGroup(
                "mozilla.general", "Mozilla General", newInboundArticlesReceiveLimit, cachedInboundArticlesLimit);
            cache.SaveChanges();
            return group;
        }

        internal static IGroup CreateGccDeveloperGroup(
            Cache cache, IHost host, int newInboundArticlesReceiveLimit, int cachedInboundArticlesLimit)
        {
            var group = host.AddGroup(
                "mozilla.tools", "Mozilla Tools", newInboundArticlesReceiveLimit, cachedInboundArticlesLimit);
            cache.SaveChanges();
            return group;
        }

        internal static IHost CreateTestHost(Cache cache)
        {
            TestServerHelper.GetTestServerClient().Dispose();
            var host = cache.AddHost<NntpForumProvider, NntpConnection>(
                TestServerHelper.ServerName,
                119,
                "Andreas Huber",
                "ahd6974-spamboostorgtrap@yahoo.com",
                TestServerHelper.AccountName,
                TestServerHelper.Password);
            cache.SaveChanges();
            return host;
        }

        internal static IGroup CreateTestGroup(
            Cache cache, IHost host, int newInboundArticlesReceiveLimit, int cachedInboundArticlesLimit)
        {
            var group = host.AddGroup("alt.test", "Test", newInboundArticlesReceiveLimit, cachedInboundArticlesLimit);
            cache.SaveChanges();
            return group;
        }

        internal static Message CreateTestMessage()
        {
            return new Message()
            {
                Fields = { new UnstructuredHeaderField("Newsgroups") { Body = "alt.test" } },
                From = new List<Address>() { new Address("Andreas Huber", "ahd6974-spamboostorgtrap@yahoo.com") },
                Subject = Guid.NewGuid().ToString("N"),
                Body = new TextBody(TextSubtypeNames.Plain) { Content = "ignore" }
            };
        }

        internal static Task Receive(Cache cache, bool receiveContents, ConsoleProgressObserver observer)
        {
            var group = cache.AddHost<NntpForumProvider, NntpConnection>(
                "localhost", 119).AddGroup(OurGroupName, "Our Group", 100, 100);
            cache.SaveChanges();
            return cache.SendReceiveAsync(new[] { group }, receiveContents, CancellationToken.None, observer);
        }

        internal static void WaitForSendReceiveToFinish(
            Cache cache, Task sendReceiveTask, ConsoleProgressObserver observer)
        {
            using (sendReceiveTask)
            {
                try
                {
                    sendReceiveTask.Wait();
                }
                catch (AggregateException ex)
                {
                    if (sendReceiveTask.IsCanceled)
                    {
                        observer.OnStopped();
                    }
                    else
                    {
                        foreach (var exception in ex.Flatten().InnerExceptions)
                        {
                            observer.OnException(exception);
                        }
                    }
                }
            }

            cache.Reset();
            observer.OnFinished();
            observer.RethrowException();
        }

        /// <summary>Represents a crypto provider that does not encrypt.</summary>
        internal sealed class NullCryptoProvider : ICryptoProvider
        {
            /// <summary>See <see cref="ICryptoProvider.Encrypt"/>.</summary>
            public byte[] Encrypt(byte[] input)
            {
                return input;
            }

            /// <summary>See <see cref="ICryptoProvider.Decrypt"/>.</summary>
            public byte[] Decrypt(byte[] input)
            {
                return input;
            }
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        private int GetRandomPort()
        {
            return this.Random.Next(1, short.MaxValue + 1);
        }

        private int GetRandomNewInboundArticlesReceiveLimit()
        {
            return this.Random.Next(1, int.MaxValue);
        }

        private bool? GetRandomBool()
        {
            switch (this.Random.Next(3))
            {
                case 0:
                    return null;
                case 1:
                    return false;
                default:
                    return true;
            }
        }

        private static bool? Shift(bool? value)
        {
            switch (value)
            {
                case null:
                    return false;
                case false:
                    return true;
                default:
                    return null;
            }
        }

        private static void AssertEqual(
            IGroup group,
            string hostName,
            string name,
            string displayName,
            int newInboundArticlesReceiveLimit,
            int cachedInboundArticlesLimit,
            InboundArticleFilter filter)
        {
            Assert.AreEqual(hostName, group.Host.Name);
            Assert.AreEqual(name, group.Name);
            Assert.AreEqual(displayName, group.DisplayName);
            Assert.AreEqual(newInboundArticlesReceiveLimit, group.NewInboundArticlesReceiveLimit);
            Assert.AreEqual(cachedInboundArticlesLimit, group.CachedInboundArticlesLimit);
            Assert.AreEqual(filter, group.InboundArticleFilter);
            Assert.AreEqual(filter.GetHashCode(), group.InboundArticleFilter.GetHashCode());
        }
    }
}
